package utils;
import graph.AssignNode;
import graph.BlockNode;
import graph.BoolValueNode;
import graph.Graph;
import graph.IfNode;
import graph.Node;
import graph.OperatorNode;
import graph.ValueNode;
import graph.VariableNode;
import graph.WhileNode;

import java.util.Iterator;
import java.util.ListIterator;
import java.util.Map;

/**
 * A class containing a utility method merge(). This method takes the program graph
 * and adds to it the properties node, while correcting the references.
 *
 */


public class MergeGraph {
	
	Graph graph;
	Graph property;	
	
	/**
	 * @param graph
	 * @param property
	 */
	public MergeGraph(Graph graph, Graph property) {
		super();
		this.graph = graph.clone();
		this.property = property.clone();
		
	}

	/**
	 * Merge the graph and the property and returns the resultant graph.
	 * Not that neither the original graph nor the original property will be modified.
	 * @return
	 * @throws Exception
	 */
	public Graph merge() throws Exception
	{
		
		Map<String,VariableNode> graphVariablesMap = graph.getDeclaredVariables();
		
		merge2(property.getRoot(), graphVariablesMap);
		
		graph.setProperty(property.getRoot());
		
		return graph;
			
	}
	
	/**
	 * Helper method
	 * 
	 * @param node
	 * @param varMap
	 * @return
	 * @throws Exception
	 */
	private Node merge2(Node node, Map<String,VariableNode> varMap) throws Exception
	{
		if(node instanceof VariableNode)
		{
			VariableNode temp = (VariableNode)node;
			String ID = temp.getID();
			
			if(!varMap.containsKey(ID))
				throw new Exception("The Property file reference an " +
						"undeclared variable in the program: " + ID);
			return varMap.get(ID);
		}
		else if(node instanceof OperatorNode)
		{
			OperatorNode temp = (OperatorNode)node;
			temp.setLeftOperand((ValueNode) merge2(temp.getLeftOperand(),varMap));
			temp.setRightOperand((ValueNode) merge2(temp.getRightOperand(),varMap));
			return temp;
		}
		else if(node instanceof BlockNode)
		{
			BlockNode temp = (BlockNode)node;
			ListIterator<Node> it = temp.iterator();
			while(it.hasNext())
			{
				Node n = it.next();
				it.set(merge2(n,varMap));
			}
			return temp;
		}
		else if(node instanceof IfNode)
		{
			IfNode temp = (IfNode)node;
			temp.setConditionNode((BoolValueNode) merge2((Node)temp.getConditionNode(),varMap));
			if(temp.getThenNode()!=null)
				temp.setThenNode((BlockNode) merge2(temp.getThenNode(),varMap));
			if(temp.getElseNode()!=null)
				temp.setElseNode((BlockNode) merge2(temp.getElseNode(),varMap));
			return temp;
		}
		else if(node instanceof WhileNode)
		{
			WhileNode temp = (WhileNode)node;
			temp.setConditionNode((BoolValueNode) merge2((Node)temp.getConditionNode(),varMap));
			temp.setDoNode((BlockNode) merge2(temp.getDoNode(),varMap));
			return temp;
		}
		else if(node instanceof AssignNode)
		{
			AssignNode temp = (AssignNode)node;
			temp.setValueNode((ValueNode) merge2(temp.getValueNode(),varMap));
			temp.setVariableNode((VariableNode) merge2(temp.getVariableNode(),varMap));
			return temp;
		}
		else
		{
			return node;
		}
	}

}
